import pandas as pd
from datetime import datetime, time
import tkinter as tk  # 导入Tkinter库，用于创建图形用户界面
import sys
from tkinter import filedialog  # 导入Tkinter库中的filedialog模块，用于打开文件对话框
import os, re


class Orders_filter():
    def __init__(self):
        self.stations = ['洛阳', '龙门', '郑州西', '巩义南', '渑池南', '三门峡南', '灵宝西', '关林', '洛阳东',
                         '洛阳北']

    def shangxing_format_xiegang_checi(self, train):
        '''处理包含字符“/”的列车基础信息，分成2到4个车次
        all_trains是存储列车信息的空列表
        s是待处理的基础列车信息的字符串
        '''

        if '(' in train or '（' in train:
            return train
        xiegang_indexs = [i for i in range(len(train)) if train[i] == '/']  # 列表推导式 获取“/”在字符串中的位置列表。
        # 包含 2、3个/ 且 两斜杠间的数字数量不同
        if len(xiegang_indexs) == 2 and xiegang_indexs[1] - xiegang_indexs[0] != len(train[xiegang_indexs[1] + 1:]) + 1:
            return train
        elif len(xiegang_indexs) == 3 and not xiegang_indexs[1] - xiegang_indexs[0] == xiegang_indexs[2] - \
                                              xiegang_indexs[
                                                  1] == len(train[xiegang_indexs[2] + 1:]) + 1:
            return train
        if '/' in train:
            trains = []
            b = list(train)  # 转换成列表，便于倒排，倒排后可解决有多个“/"导致判断不准确的问题（G8780/78/80)
            b.reverse()
            xie_index = b.index('/')  # 获取列表中/首次出现的下标
            bianhua_len = len(b[:xie_index])  # 判断切片的长短，即是 复用车次的长度
            xiegang_index = train.find('/')  # 获取/首次出现在字符串中的位置
            trains.append(train[:xiegang_index])

            # 存在57021/2/3/4，四个车次的情况，需要分解后添加列车。斜杠间有1、2、3位数字的情况。都要考虑到
            if bianhua_len == 1:  # 如果/后面有一个数字时，先前取一位，再加上/后的1位。
                if len(xiegang_indexs) == 1:  # 有1个/时 取车号的规则
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 1])
                elif len(xiegang_indexs) == 2:  # 有2个/时 取车号的规则
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 1])
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 3])
                else:  # 有3个/时 取车号的规则
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 1])
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 3])
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 5])
            elif bianhua_len == 2:  # 如果/后面有2个数字时，先前取2位，再加上/后的2位。
                if len(xiegang_indexs) == 1:
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 1:xiegang_index + 3])
                elif len(xiegang_indexs) == 2:
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 1:xiegang_index + 3])
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 4:xiegang_index + 6])
                else:
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 1:xiegang_index + 3])
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 4:xiegang_index + 6])
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 7:xiegang_index + 9])
            else:  # 如果/后面有3个数字时，先前取3位，再加上/后的3位。（9月7日G1900/897。3位的车次很少，目前仅发现了一次）,目前没有发现4个及以上的。
                if len(xiegang_indexs) == 1:
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 1:xiegang_index + 4])
                elif len(xiegang_indexs) == 2:
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 1:xiegang_index + 4])
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 5:xiegang_index + 8])
                else:
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 1:xiegang_index + 4])
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 5:xiegang_index + 8])
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 9:xiegang_index + 12])
            try:
                for train1 in trains:
                    if train1[-1].isdigit():
                        if int(train1[-1]) % 2 == 0:
                            return train1
                    elif train1[-2].isdigit():
                        if int(train1[-2]) % 2 == 0:
                            return train1
            except:
                return train

                # if train1[-3].isdigit():
                #     if int(train1[-3]) % 2 == 1:
                #         return train1
        else:
            return train

    def xiaxing_format_xiegang_checi(self, train):
        '''处理包含字符“/”的列车基础信息，分成2到4个车次
        all_trains是存储列车信息的空列表
        s是待处理的基础列车信息的字符串
        '''

        if '(' in train or '（' in train:
            return train
        xiegang_indexs = [i for i in range(len(train)) if train[i] == '/']  # 列表推导式 获取“/”在字符串中的位置列表。
        # 包含 2、3个/ 且 两斜杠间的数字数量不同
        if len(xiegang_indexs) == 2 and xiegang_indexs[1] - xiegang_indexs[0] != len(train[xiegang_indexs[1] + 1:]) + 1:
            return train
        elif len(xiegang_indexs) == 3 and not xiegang_indexs[1] - xiegang_indexs[0] == xiegang_indexs[2] - \
                                              xiegang_indexs[
                                                  1] == len(train[xiegang_indexs[2] + 1:]) + 1:
            return train
        if '/' in train:
            trains = []
            b = list(train)  # 转换成列表，便于倒排，倒排后可解决有多个“/"导致判断不准确的问题（G8780/78/80)
            b.reverse()
            xie_index = b.index('/')  # 获取列表中/首次出现的下标
            bianhua_len = len(b[:xie_index])  # 判断切片的长短，即是 复用车次的长度
            xiegang_index = train.find('/')  # 获取/首次出现在字符串中的位置
            trains.append(train[:xiegang_index])

            # 存在57021/2/3/4，四个车次的情况，需要分解后添加列车。斜杠间有1、2、3位数字的情况。都要考虑到
            if bianhua_len == 1:  # 如果/后面有一个数字时，先前取一位，再加上/后的1位。
                if len(xiegang_indexs) == 1:  # 有1个/时 取车号的规则
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 1])
                elif len(xiegang_indexs) == 2:  # 有2个/时 取车号的规则
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 1])
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 3])
                else:  # 有3个/时 取车号的规则
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 1])
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 3])
                    trains.append(train[:xiegang_index - 1] + train[xiegang_index + 5])
            elif bianhua_len == 2:  # 如果/后面有2个数字时，先前取2位，再加上/后的2位。
                if len(xiegang_indexs) == 1:
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 1:xiegang_index + 3])
                elif len(xiegang_indexs) == 2:
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 1:xiegang_index + 3])
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 4:xiegang_index + 6])
                else:
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 1:xiegang_index + 3])
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 4:xiegang_index + 6])
                    trains.append(train[:xiegang_index - 2] + train[xiegang_index + 7:xiegang_index + 9])
            else:  # 如果/后面有3个数字时，先前取3位，再加上/后的3位。（9月7日G1900/897。3位的车次很少，目前仅发现了一次）,目前没有发现4个及以上的。
                if len(xiegang_indexs) == 1:
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 1:xiegang_index + 4])
                elif len(xiegang_indexs) == 2:
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 1:xiegang_index + 4])
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 5:xiegang_index + 8])
                else:
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 1:xiegang_index + 4])
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 5:xiegang_index + 8])
                    trains.append(train[:xiegang_index - 3] + train[xiegang_index + 9:xiegang_index + 12])
            try:
                for train1 in trains:
                    if train1[-1].isdigit():
                        if int(train1[-1]) % 2 == 1:
                            return train1
                    elif train1[-2].isdigit():
                        if int(train1[-2]) % 2 == 1:
                            return train1
            except:
                return train
        else:
            return train

    def get_filepath(self):
        # 调用tkinter 获取选择的文件
        root = tk.Tk()  # 创建了一个Tkinter窗口对象，并将其赋值给变量root。
        root.withdraw()  # 隐藏窗口。当你创建一个Tkinter窗口对象后，默认情况下它会被显示出来。使用withdraw()方法会隐藏这个窗口，用户将看不到它。
        # 使用filedialog模块打开一个文件选择对话框，允许用户选择多个文件
        file_paths = filedialog.askopenfilenames(title="选择需要处理的文件")  # 先择需要处理的文件，赋值给变量file_paths 列表
        if len(file_paths) == 0:  # 如果没有选择文件，退出程序
            sys.exit()
        return file_paths  # 返回选择的文件元组('C:/临客排序/1月11-20日临客通知1-2  - 副本.xlsx', 'C:/临客排序/1月11-20日临客通知1-2 (排序后).xlsx')

    # def get_sheet_names(self, path):
    #     '''
    #     获取工作薄中 所有工作表的名字
    #     :param path: 工作薄的路径 如'C:/Users/86150/Desktop/临客排序/1月11-20日临客通知1-2  - 副本.xlsx'等
    #     :return: path工作薄中的所有工作表的列表[sheeet1,sheet2]
    #     '''
    #     excel_file = pd.ExcelFile(path)  # 用pandas建立excle_file对象
    #     # 获取所有工作表的名称
    #     return excel_file

    def get_sheet_names(self, path):
        '''
        获取工作薄中 所有工作表的名字
        :param path: 工作薄的路径 如'C:/Users/86150/Desktop/临客排序/1月11-20日临客通知1-2  - 副本.xlsx'等
        :return: path工作薄中的所有工作表的列表[sheeet1,sheet2]
        '''
        excel_file = pd.ExcelFile(path)  # 用pandas建立excle_file对象
        # 获取所有工作表的名称
        return excel_file.sheet_names  # 调用 excel_file.sheet_names属性 返回一个包含所有工作表名称的列表

    def strf_time(self, cell):
        '''
        统一处理单元格：将时间格式转换为 字符格式，将int类型转换为 str，去掉*符号头
        :param time: 单元格的值
        :return: 符合预期类型和处理过的单元格
        '''
        if type(cell) == time:  # 如何类型为时间time格式
            return cell.strftime('%H:%M')  # 将time格式改为str格式
        elif type(cell) == int:  # 运行文件中读取的 4707 等数字车次，是int类型 ，而 命令中的 4707是str类型，故转为str
            return str(cell)
        elif type(cell) == str and cell.startswith('*'):  # 如何类型为时间time格式
            return cell.replace('*', '')  # 去掉开头的*号
        else:
            return cell

    # def del_xingHao(self, train_num):
    #     '''
    #     删除车次前的 *
    #     :param train_num: 列车车次
    #     :return: 没有 * 的列车车次
    #     '''
    #     if type(train_num) == str and train_num.startswith('*'):  # 如何类型为时间time格式
    #         return train_num.replace('*', '')  # 将time格式改为str格式
    #     else:
    #         return train_num

    def get_one_sheetTrain(self, path, sheet_name):
        '''
        获取一个工作表内的列车车次、始发终到站、到、开时刻。
        :param sheet_name: 工作表的名称
        :return: 工作表的字典，车次为键  始发终到站、到、开时刻列表为值。
        '''
        # keep_default_na=False 将空值保存为'',而不是NAN
        df = pd.read_excel(path, sheet_name=sheet_name, header=2, keep_default_na=False)
        df = df.iloc[:, [1, 2, 3, 4]]  # 选择我们需要的2，3，4，5列
        df = df.applymap(self.strf_time)
        trains = df.values.tolist()  # 将pd数据 转换为py收据 列表
        train_lis = [train for train in trains if train[0] != '车次' and train[1] != '']

        return train_lis

    def get_tu(self):
        '''
        获取洛阳车站春运期间临客和基本图信息
        :return: 基本图和春运图各站办客列车的字典
        '''

        normal_trains = {}  # 基本图 字典
        spring_trains = {}  # 春运图 字典

        # 获取春运图 列车基本信息
        spring_path = os.path.join(os.getcwd(),
                                   r'2025年春运-各种补充文件纳入后的-旅客列车运行时刻表\2025年春运临客.xlsx')
        spring_sheet_names = self.get_sheet_names(spring_path)  # 获取春运图工作表名字 列表
        for sheet_name in spring_sheet_names:  # 遍历列表 调用函数 获取该列表的数据 存入 列表名为键的字典
            spring_trains[sheet_name] = self.get_one_sheetTrain(spring_path, sheet_name)

        # 获取基本图 列车基本信息
        normal_path = os.path.join(os.getcwd(),
                                   r'2025年春运-各种补充文件纳入后的-旅客列车运行时刻表\2025年季度基本图.xlsx')
        normal_sheet_names = self.get_sheet_names(normal_path)  # 获取基本图工作表名字 列表
        for sheet_name in normal_sheet_names:  # 遍历列表 获取 获取单个列表的列车信息 存入列表名为键的字典
            normal_trains[sheet_name] = self.get_one_sheetTrain(normal_path, sheet_name)
            if sheet_name in spring_trains:  # 如果春运图 中 该站有临客 则 将春运图 中该站的临客信息 合并到基本图列车信息中
                normal_trains[sheet_name].extend(spring_trains[sheet_name])

        # 将所有列车信息转换为 {'洛阳龙门站': {'G7912': ['洛阳龙门—濮阳东', '始发', '06:50'],字典。
        all_trains = {}  # 按 站 存储列车信息
        for station, train_lis in normal_trains.items():
            station_dic = {}
            for train in train_lis:
                station_dic[train[0]] = train[1:]
            all_trains[station] = station_dic

        return all_trains

    # def get_sheet_names(self, path):
    #     '''
    #     获取工作薄中 所有工作表的名字
    #     :param path: 工作薄的路径 如'C:/Users/86150/Desktop/临客排序/1月11-20日临客通知1-2  - 副本.xlsx'等
    #     :return: path工作薄中的所有工作表的列表[sheeet1,sheet2]
    #     '''
    #     excel_file = pd.ExcelFile(path)  # 用pandas建立excle_file对象
    #     # 获取所有工作表的名称
    #     return excel_file.sheet_names  # 调用 excel_file.sheet_names属性 返回一个包含所有工作表名称的列表

    def to_int(self, order_cell):
        '''
        将数字转换为int类型，转换失败 则返回原值。不用判断数据类型。
        :return:
        '''
        try:
            return int(order_cell)
        except:
            return order_cell

    def re_rows(self, text):
        '''
        匹配调度命令中的车次
        :param text:带配备的字符串
        :return:匹配到的所有列车车次
        '''
        patterns = [r'[GKTZDCgktzdc]\d+/?\d+/?\d*', r'470\d+/?\d?/?\d?', r'57019', r'57020']
        trains = []

        # 遍历所有的 匹配模式，匹配目标字符
        for pattern in patterns:
            match = re.findall(pattern, text)  # 匹配所有符合要求的字符
            trains.extend(match)  # 全部存入trains列表

        return trains

    def get_order_rows(self, paths):
        '''
        获取命令的原始文本
        :param paths: 命令的存储路径
        :return: 命令名为键，所有正文行为列表为值的字典
        '''
        order_content_dic = {}  # 存储所有选择的命令excle文档 行的文本列表为值，命令号为键

        # 遍历选择的路径列表，分别获取单个命令excle的行文本，存入命令号为键的字典
        for path in paths:
            st_name = self.get_sheet_names(path)[0]
            df = pd.read_excel(path, sheet_name=st_name, header=2, keep_default_na=False)
            df = df.applymap(self.to_int)  # 将数字转换为int类型，之后需要根据数量类型进行筛选
            rows = df.values.tolist()   # 将df数据转换为python列表
            orders_lis = [text for text in rows if type(text[1]) == int]  # 筛选出带int类型行号的行，这是我们想要的正文
            order_content_dic[st_name] = orders_lis  # 将正文内容存入 命令号为键的字典

        return order_content_dic

    def train_stop_in_luoyang(self, train):
        """判断train是否在洛阳站的车次表内
        如果在则标识出来
        """
        relation_trains = []  # 与洛阳站相关的 列车

        # 遍历 各站车次库，查看 train是否在某个站的车次中，如果在 则 将该次列车所在的车站、列车车次、到开时间、始发终到站等等信息 存起来
        for station, trains in self.all_trains.items():
            if train in trains.keys():
                train_lis = []
                train_lis.append(station)
                train_lis.append(train)
                train_lis.extend(trains[train])
                relation_trains.append(train_lis)

        return relation_trains  # 返回 关系站 的车站 车次 到开时刻等

    def stationName_In_row(self, row):
        '''
        返回包含洛阳站管内站的 正文
        :param row: 调度命令 正文行
        :return:
        '''
        relation_row = []

        for station in self.stations:
            one_station = []
            if station in row:
                one_station.append(station)
                lis = row.split(station)
                str = lis[0] + station
                one_station.append(str)
            if len(one_station) > 0:
                relation_row.append(one_station)
        return relation_row

    def merge_dicts_with_same_key(self, dict1, dict2):
        '''
        合并两个字典 并排序
        :param dict1:
        :param dict2:
        :return:
        '''

        merged_dict = {}
        # 如果字典中没有对应的键，可以创建它或者初始化空字典
        for key in dict1:
            if key in dict2:  # 如果键在字典中存在
                dict1[key].extend(dict2[key])
                merged_dict[key] = dict1[key]  # 合并值
            else:
                merged_dict[key] = dict1[key]
        for key in dict2:
            if key not in dict1:
                merged_dict[key] = dict2[key]
        sorted_dict = {k: merged_dict[k] for k in sorted(merged_dict.keys())}  # 字典排序
        return sorted_dict

    def get_order_content(self):
        '''
        :param path:命令的路径
        :return: 命令的内容
        '''
        paths = self.get_filepath()  # 获取需要解码的命令路径
        # 获取 命令号码为键 命令正文 行列表为值的字典
        order_content_dic = self.get_order_rows(paths)  # {'50227': [['', 1, '1月25日到郑州K4426次

        self.all_trains = self.get_tu()  # 各站列车时刻字典 {'洛阳龙门站': {'G7912': ['洛阳龙门—濮阳东', '始发', '06:50'],

        # 存储命令号 为键 涉及洛阳站的行:列车信息字典
        all_orders_res_dic = {}  # {'50227': {4: [['洛阳站', 'K5356', '洛阳—郑州', '', '18:22']],
        for order_num, row_text in order_content_dic.items():
            relation_trains_dic = {}  # 含有与车站相关的列车信息的字典
            relation_stations_dic = {}  # 含有与车站管内车站信息的行的字典

            for row in row_text:
                searched_trains = list(set(self.re_rows(row[2])))
                one_relation_trains = []
                if len(searched_trains) > 0:  # 如果有匹配的车次 则 分解车次

                    divide_trains = []
                    for train in searched_trains:
                        divide_trains.append(self.xiaxing_format_xiegang_checi(train))
                        divide_trains.append(self.shangxing_format_xiegang_checi(train))
                    divide_trains = list(set(divide_trains))
                    if row[1] == 13:
                        print(11111)

                        print(divide_trains)

                    for searched_train in divide_trains:
                        trains = self.train_stop_in_luoyang(searched_train)  # 所有 挑出来的车.
                        one_relation_trains.extend(trains)

                if len(one_relation_trains) > 0:

                    if row[1] in relation_trains_dic:
                        relation_trains_dic[row[1]].extend(one_relation_trains)
                    else:
                        relation_trains_dic[row[1]] = one_relation_trains

                #  通过站名是否在命名中提到 来筛选
                row_relation_stations = self.stationName_In_row(row[2])
                if len(row_relation_stations) > 0:
                    # 解决行覆盖的问题 两个或多个1行，2行等。
                    if row[1] in relation_stations_dic:  # 如果 该键已经存在 在将值增加到 该键的值中，否则 会被覆盖。如当出现两个1行号时
                        relation_stations_dic[row[1]].extend(row_relation_stations)
                    else:
                        relation_stations_dic[row[1]] = row_relation_stations

            order_dic = self.merge_dicts_with_same_key(relation_trains_dic, relation_stations_dic)

            if len(order_dic) == 0:
                order_dic['无洛阳站相关内容'] = '无'
            all_orders_res_dic[order_num] = order_dic
        self.outPut(all_orders_res_dic)

    def currenttime_minute(self):
        '''获取当前的年月日'''
        try:
            # 获取当前日期和时间
            current_datetime = datetime.now()
            # 获取年份
            year = current_datetime.year
            # 获取月份
            month = current_datetime.month
            # 获取日
            day = current_datetime.day
            # 获取小时
            hour = current_datetime.hour  # 或者使用 datetime.time.hour
            # 获取分钟
            minute = current_datetime.minute  # 或者使用 datetime.time.minute 或 datetime.timedelta(minutes=...) 来获取精确的小时部分

            return f'{year}-{month}-{day} {hour}：{minute}'
        except:
            pass

    def outPut(self, order_dic):

        # 将结果输入到word
        str = ''  # 输入的字符串
        num = 1
        for order_num, dic_res in order_dic.items():
            big_num = self.num2chinese(num)
            str += f'\t{big_num}、{order_num}号命令：\n'
            if '无洛阳站相关内容' in dic_res:
                num += 1
                str += '\t无\n'
            else:
                small_num = 1
                for row_num, res_lis in dic_res.items():
                    row_str = ''
                    for oneTrain in res_lis:
                        if len(oneTrain) == 5:
                            row_str += f"\t{' '.join(oneTrain)}" + '\n'
                        else:
                            row_str += f"\t{'->'.join(oneTrain)}" + '\n'

                    str += f'\t{small_num}.第{row_num}项中涉及洛阳站的内容有>>：\n\t{row_str}'
                    small_num += 1
                num += 1
        time = self.currenttime_minute()

        desktop_path = os.path.join(os.path.expanduser("~"),
                                    f"Desktop\{time}-{list(order_dic.keys())[0]}.doc")  # 获取桌面路径 并建立保存文件的路径
        with open(desktop_path, "w", encoding='utf-8') as f:
            print(str, file=f)

        # return str

    def num2chinese(self, num):
        chinese_num = '零一二三四五六七八九'
        units = ['', '十', '百', '千']
        big_units = ['', '万', '亿', '万亿']

        # 获取 当前日期
        def four_digit_to_chinese(num):
            if num == 0: return ''
            result = ''
            for i in range(4):
                digit = num % 10
                num //= 10
                if digit != 0:
                    result = chinese_num[digit] + units[i] + result
                elif result and result[0] != '零':
                    result = result
            return result

        if num == 0: return '零'
        result = ''
        for i in range(len(big_units)):
            num_str = str(num)
            if len(num_str) > i * 4:
                part = four_digit_to_chinese(int(num_str[-4:]))
                if part:
                    result = part + big_units[i] + result
                elif result and result[0] != '零':
                    result = '零' + result
            if num < 10: break
            num //= 10000
        return result


order_folter_2025 = Orders_filter()
order_folter_2025.get_order_content()
